package com.qiangesoft.im.exception.advice;

import com.qiangesoft.im.exception.BaseException;
import com.qiangesoft.im.exception.result.ResultMessageEnum;
import com.qiangesoft.im.pojo.vo.ResultInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.nio.file.AccessDeniedException;
import java.util.List;
import java.util.Set;

/**
 * 全局异常处理
 *
 * @author qiangesoft
 * @date 2023-09-18
 */
@Slf4j
@RestControllerAdvice
public class ExceptionHandlerAdvice {

    /**
     * 权限异常
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(AccessDeniedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public ResultInfo<ResultMessageEnum> handleAccessDeniedException(AccessDeniedException exception) {
        exception.printStackTrace();
        return ResultInfo.fail(ResultMessageEnum.FORBIDDEN);
    }

    /**
     * 请求方式异常
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public ResultInfo<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException exception, HttpServletRequest request) {
        exception.printStackTrace();
        String method = request.getMethod();
        return ResultInfo.fail(ResultMessageEnum.METHOD_NOT_ALLOWED.getCode(), String.format("请求方式%s不支持", method));
    }

    /**
     * 参数合法性校验异常
     * {@link BindException}（以form-data形式传参）
     * {@link MethodArgumentNotValidException}（常以body传参）
     *
     * @param exception
     * @return
     */
    @ExceptionHandler({BindException.class, MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResultInfo<Void> handleBindException(BindException exception) {
        exception.printStackTrace();
        List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
        StringBuilder message = new StringBuilder();
        for (FieldError fieldError : fieldErrors) {
            message.append(fieldError.getField()).append(fieldError.getDefaultMessage()).append(";");
        }
        return ResultInfo.fail(ResultMessageEnum.PARAM_ERROR.getCode(), message.toString());
    }

    /**
     * 参数合法性校验异常（通常是query或者form-data传参时的异常）
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResultInfo<Void> handleConstraintViolationException(ConstraintViolationException exception) {
        exception.printStackTrace();
        Set<ConstraintViolation<?>> constraintViolations = exception.getConstraintViolations();
        StringBuilder message = new StringBuilder();
        for (ConstraintViolation<?> constraintViolation : constraintViolations) {
            message.append(constraintViolation.getPropertyPath()).append(constraintViolation.getMessage()).append(";");
        }
        return ResultInfo.fail(ResultMessageEnum.PARAM_ERROR.getCode(), message.toString());
    }

    /**
     * ValidationException异常
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(value = ValidationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResultInfo<ResultMessageEnum> handleValidationException(ValidationException exception) {
        exception.printStackTrace();
        return ResultInfo.fail(ResultMessageEnum.PARAM_ERROR);
    }

    /**
     * 参数校验异常（以@RequestParam的传参的校验）
     * 例如：接口上设置了@RequestParam("xx")参数，结果并未传递xx参数
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResultInfo<Void> handleMissingServletRequestParameterException(MissingServletRequestParameterException exception) {
        exception.printStackTrace();
        return ResultInfo.fail(exception.getMessage());
    }

    /**
     * 参数类型异常（通常为表单传参错误导致参数无法类型转换）
     * 例如：接口上设置了@RequestParam("xx")参数为Integer，结果传递xx参数类型为String
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResultInfo<Void> handleMissingServletRequestParameterException(MethodArgumentTypeMismatchException exception) {
        exception.printStackTrace();
        return ResultInfo.fail(ResultMessageEnum.PARAM_TYPE_ERROR.getCode(), exception.getName() + ResultMessageEnum.PARAM_TYPE_ERROR.getMessage());
    }

    /**
     * 参数不可读异常（通常为传参错误导致参数无法解析映射实体属性，body传参时参数类型错误也会进来）
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResultInfo<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException exception) {
        exception.printStackTrace();
        return ResultInfo.fail(ResultMessageEnum.MESSAGE_NOT_READABLE.getCode(), exception.getMessage());
    }

    /**
     * MediaType不支持异常（通常为body传参时contentType错误）
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    public ResultInfo<Void> handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException exception) {
        exception.printStackTrace();
        return ResultInfo.fail(ResultMessageEnum.BODY_MEDIA_TYPE_NOT_SUPPORT.getCode(), exception.getMessage());
    }

    /**
     * 【业务校验】过程中的非法参数异常
     * 该异常基本由{@link org.springframework.util.Assert}抛出
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.OK)
    public ResultInfo<Void> handleIllegalArgumentException(IllegalArgumentException exception) {
        exception.printStackTrace();
        return ResultInfo.fail(ResultMessageEnum.INTERNAL_SERVER_ERROR.getCode(), exception.getMessage());
    }

    /**
     * 业务异常
     * 该异常基本由{@link BaseException}及其子类抛出
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(BaseException.class)
    @ResponseStatus(HttpStatus.OK)
    public ResultInfo<Void> handleBaseException(BaseException exception) {
        exception.printStackTrace();
        return ResultInfo.fail(exception.getCode(), exception.getMessage());
    }

    /**
     * 未知异常和错误（兜底处理）
     *
     * @param throwable
     * @return
     */
    @ExceptionHandler(Throwable.class)
    @ResponseStatus(HttpStatus.OK)
    public ResultInfo<ResultMessageEnum> handleThrowable(Throwable throwable) {
        throwable.printStackTrace();
        return ResultInfo.fail(ResultMessageEnum.INTERNAL_SERVER_ERROR);
    }
}